﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace Hont
{
    public class NodeAStar
    {
        Node mBeginNode;
        Node mTargetNode;
        List<Node> mOpenList;
        List<Node> mOpenContainDetecteList;
        List<Node> mClosedList;
        IAStarHCost mHCost;
        int mGCost;


        public NodeAStar() : this(new GenericNodeAStarHCost())
        {
        }

        public NodeAStar(IAStarHCost aStarCost) : this(aStarCost, 10)
        {
        }

        public NodeAStar(IAStarHCost hCost, int gCost)
        {
            this.mHCost = hCost;
            this.mGCost = gCost;
        }

        public void Init()
        {
            mOpenList = new List<Node>();
            mOpenContainDetecteList = new List<Node>();
            mClosedList = new List<Node>();
        }

        public Node[] Start(Node beginNode, Node targetNode, int mask = -1)
        {
            mOpenList.Clear();
            mOpenContainDetecteList.Clear();
            mClosedList.Clear();
            mBeginNode = beginNode;
            mTargetNode = targetNode;

            //Init first node.
            mBeginNode.Init(0, mHCost.GetCost(mBeginNode.transform.position, mTargetNode.transform.position), mBeginNode.Walkable, 1);

            var searchResult = Search(mask);
            if (searchResult)
            {
                var nodeArray = Node2Array(mTargetNode);
                return nodeArray;
            }

            return null;
        }

        bool NodeIsWalkable(Node currentNode, Node compareNode, int mask)
        {
            bool result = false;

            if (compareNode.Walkable
                && compareNode.CheckMask(mask)
                && !mClosedList.Contains(compareNode)
                && !mOpenContainDetecteList.Contains(compareNode))
            {
                result = true;
            }

            return result;
        }

        int GetGValue(Node currentNode, Node compareNode)
        {
            var result = mGCost;

            result += currentNode.G + compareNode.Cost;

            return result;
        }

        bool Search(int mask)
        {
            var result = true;
            var currentNode = mBeginNode;
            mClosedList.Add(mBeginNode);

            while (currentNode != mTargetNode && result)
            {
                for (int i = 0, iMax = currentNode.siblingNodes.Length; i < iMax; i++)
                {
                    var siblingNode = currentNode.siblingNodes[i];

                    if (!NodeIsWalkable(currentNode, siblingNode, mask)) continue;

                    siblingNode.G = GetGValue(currentNode, siblingNode);
                    siblingNode.H = mHCost.GetCost(siblingNode.transform.position, mTargetNode.transform.position);
                    siblingNode.Parent = currentNode;
                    mOpenList.Add(siblingNode);
                    mOpenContainDetecteList.Add(siblingNode);
                }

                result = mOpenList.Count > 0;//Has remain road.

                if (result)
                {
                    mOpenList.Sort((x, y) => x.F.CompareTo(y.F));
                    currentNode = mOpenList[0];

                    mOpenList.Remove(currentNode);
                    mOpenContainDetecteList.Remove(currentNode);
                    mClosedList.Add(currentNode);
                }
            }

            return result;
        }

        Node[] Node2Array(Node node)
        {
            var resultList = new List<Node>();
            var tNode = node;

            resultList.Add(tNode);

            while (tNode != mBeginNode)
            {
                tNode = tNode.Parent;

                resultList.Add(tNode);
            }

            resultList.Reverse();

            return resultList.ToArray();
        }
    }
}
